package org.example.spring.simple.framework.context;

import org.example.spring.simple.framework.annotation.ZAutowired;
import org.example.spring.simple.framework.annotation.ZController;
import org.example.spring.simple.framework.annotation.ZService;
import org.example.spring.simple.framework.aop.config.ZAopConfig;
import org.example.spring.simple.framework.aop.proxy.CgLibProxy;
import org.example.spring.simple.framework.aop.proxy.ZAopProxy;
import org.example.spring.simple.framework.aop.proxy.ZJdkDynamicAopProxy;
import org.example.spring.simple.framework.aop.support.ZAdvicedSupport;
import org.example.spring.simple.framework.beans.config.ZBeanDefinition;
import org.example.spring.simple.framework.beans.config.ZBeanPostProcessor;
import org.example.spring.simple.framework.context.support.ZBeanDefinitionReader;
import org.example.spring.simple.framework.context.support.ZDefaultListableBeanFactory;
import org.example.spring.simple.framework.core.ZBeanFactory;
import org.example.spring.simple.framework.beans.ZBeanWrapper;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ApplicationContext 是直接接触用户的入口，
 * 主要实现DefaultListableBeanFactory 的 refresh()方法
 * 和BeanFactory的getBean()方法，
 * 完成IoC、DI、AOP的衔接。
 */
public class ZApplicationContext extends ZDefaultListableBeanFactory implements ZBeanFactory {
    private String[] configLocations;
    private ZBeanDefinitionReader reader;

    //单例IoC缓存
    private Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>();
    //IoC容器
    private Map<String, ZBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();

    public ZApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void refresh() throws Exception {
        //1.定位配置文件
        reader = new ZBeanDefinitionReader(this.configLocations);
        //2.加载配置文件，将配置文件封装成BeanDefinition()
        List<ZBeanDefinition> definitionList = reader.loadBeanDefinitions();
        //3.注册，将配置信息注册到容器中
        doRegisterBeanDefinition(definitionList);
        //4.将非延迟加载的类初始化
        doAutoWired();
    }


    private void doRegisterBeanDefinition(List<ZBeanDefinition> definitionList) throws Exception {
        for (ZBeanDefinition definition: definitionList) {
            if (super.beanDefinitionMap.containsKey(definition.getFactoryBeanName())) {
                throw new Exception(definition.getFactoryBeanName() + "is already exists");
            }
            super.beanDefinitionMap.put(definition.getFactoryBeanName(), definition);
            //至此，容器初始化完毕
        }
    }

    @Override
    public Object getBean(Class<?> beanClass) throws Exception {
        return getBean(beanClass.getName());
    }

    /**
     * 开始依赖注入
     * 读取beanDefinition信息，通过反射创建一个实例并返回
     * Spring中不会把最原始的对象放出去，而是使用一个BeanWrapper进行包装
     *使用装饰器模式：1.保留原先的OOP关系  2.方便进行扩展
     * @param beanName
     * @return
     * @throws Exception
     */
    @Override
    public Object getBean(String beanName) throws Exception {
        ZBeanDefinition definition = super.beanDefinitionMap.get(beanName);
        try {
            //生成通知事件
            ZBeanPostProcessor postProcessor = new ZBeanPostProcessor();
            Object instance = instantiateBean(definition);
            if (instance == null) {
                return null;
            }
            //初始化前调用一次
            postProcessor.postProcessorBeforeInitialization(instance, beanName);
            ZBeanWrapper beanWrapper = new ZBeanWrapper(instance);
            this.factoryBeanInstanceCache.put(beanName, beanWrapper);
            //初始化后调用一次
            postProcessor.postProcessorAfterInitialization(instance, beanName);
            populateBean(beanName, instance);
            return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();
        }catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void populateBean(String beanName, Object instance) {
        Class<?> clazz = instance.getClass();
        if (!(clazz.isAnnotationPresent(ZController.class) || clazz.isAnnotationPresent(ZService.class))) {
            return;
        }
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(ZAutowired.class)) {
                continue;
            }
            ZAutowired autowired = field.getAnnotation(ZAutowired.class);
            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }
            field.setAccessible(true);
            try {
                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根据BeanDefinition返回Bean的实例
     * @param definition
     * @return
     */
    private Object instantiateBean(ZBeanDefinition definition) {
        Object instance = null;
        String className = definition.getBeanClass();
        try {
            if (this.factoryBeanObjectCache.containsKey(className)) {
                instance = this.factoryBeanObjectCache.get(className);
            } else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();
                ZAdvicedSupport config = instantiationAopConfig(definition);
                config.setTargetClass(clazz);
                config.setTarget(instance);
                if (config.pointCutMatch()) {
                    instance = createProxy(config).getProxy();
                }
                this.factoryBeanObjectCache.put(definition.getFactoryBeanName(), instance);
            }
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    //只处理非延时加载的情况
    private void doAutoWired() {
        for (Map.Entry<String, ZBeanDefinition> entry: super.beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            if (!entry.getValue().isLazyInit()) {
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig() {
        return this.reader.getConfig();
    }

    private ZAdvicedSupport instantiationAopConfig(ZBeanDefinition beanDefinition) {
        ZAopConfig aopConfig = new ZAopConfig();
        aopConfig.setPointCut(reader.getConfig().getProperty("pointCut"));
        aopConfig.setAspectClass(reader.getConfig().getProperty("aspectClass"));
        aopConfig.setAspectBefore(reader.getConfig().getProperty("aspectBefore"));
        aopConfig.setAspectAfter(reader.getConfig().getProperty("aspectAfter"));
        aopConfig.setAspectAfterThrow(reader.getConfig().getProperty("aspectAfterThrow"));
        aopConfig.setAspectAfterThrowingName(reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new ZAdvicedSupport(aopConfig);
    }

    private ZAopProxy createProxy(ZAdvicedSupport config) {
        Class targetClass = config.getTargetClass();
        if (targetClass.getInterfaces().length > 0) {
            return new ZJdkDynamicAopProxy(config);
        }
        return new CgLibProxy(config);
    }
}
